﻿using FreeRedis;

using Tszy.Unlimited.Base.Extensions;
using Tszy.Unlimited.Base.Infrastructure.Logging;

namespace Tszy.Unlimited.Base.Infrastructure.Caching.Redis;

/// <summary>
/// FreeRedis 组件缓存管理器。
/// </summary>
public class FreeRedisCacheManager : ICacheManager
{
    private readonly ILogger _logger;
    private readonly RedisClient _client;
    private static readonly object _lockObject = new();

    #region --- 初始化 ---

    /// <summary>
    /// 初始化 Redis 组件缓存管理器。
    /// </summary>
    /// <param name="connectionString">连接字符串。</param>
    public FreeRedisCacheManager(string connectionString) : this(TszyLogger.Instance, connectionString) { }

    /// <summary>
    /// 初始化 Redis 组件缓存管理器。
    /// </summary>
    /// <param name="logger"></param>
    /// <param name="connectionString">连接字符串。</param>
    public FreeRedisCacheManager(ILogger logger, string connectionString)
    {
        _logger = logger;
        _client = new RedisClient(connectionString);
    }

    /// <summary>
    /// 初始化 Redis 组件缓存管理器。
    /// </summary>
    /// <param name="host">主机地址。</param>
    /// <param name="port">端口号。</param>
    /// <param name="defaultDB">默认缓存库。</param>
    /// <param name="password">密码。</param>
    /// <param name="poolsize"></param>
    public FreeRedisCacheManager(string host, int port, int defaultDB, string password = "", int poolsize = 50) : this(TszyLogger.Instance, host, port, defaultDB, password, poolsize) { }

    /// <summary>
    /// 初始化 Redis 组件缓存管理器。
    /// </summary>
    /// <param name="logger">日志记录器。</param>
    /// <param name="host">主机地址。</param>
    /// <param name="port">端口号。</param>
    /// <param name="defaultDB">默认缓存库。</param>
    /// <param name="password">密码。</param>
    /// <param name="poolsize"></param>
    public FreeRedisCacheManager(ILogger logger, string host, int port, int defaultDB, string password = "", int poolsize = 50)
    {
        _logger = logger;
        _client = new RedisClient($"{host}:{port},password={password},defaultDatabase={defaultDB}");
        _client.Notice += (s, e) => _logger.Information(e.Log); //打印命令日志
    }

    #endregion

    /// <summary>
    /// 获得所有 Key。
    /// </summary>
    public IEnumerable<string> Keys => _client.Keys("*");

    /// <summary>
    /// 获取缓存值，如果不存在，则添加缓存。
    /// </summary>
    /// <typeparam name="TValue">值类型。</typeparam>
    /// <param name="key">键对象。</param>
    /// <param name="acquire">获得值对象的委托。</param>
    /// <returns>值对象。</returns>
    public TValue GetOrAdd<TValue>(string key, Func<TValue> acquire) => GetOrAdd(key, acquire, TimeSpan.FromMinutes(20));

    /// <summary>
    /// 获取缓存值，如果不存在，则添加缓存。
    /// </summary>
    /// <typeparam name="TValue">值类型。</typeparam>
    /// <param name="key">键对象。</param>
    /// <param name="acquire">获得值对象的委托。</param>
    /// <param name="validFor"></param>
    /// <returns>值对象。</returns>
    public TValue GetOrAdd<TValue>(string key, Func<TValue> acquire, TimeSpan validFor)
    {
        var result = Get<TValue>(key);

        if (result != null)
            return result;

        result = acquire();

        if (result != null)
            Set(key, result, validFor);

        return result;
    }

    /// <summary>
    /// 获取缓存值。
    /// </summary>
    /// <typeparam name="TValue">值类型。</typeparam>
    /// <param name="key">键对象。</param>
    /// <returns>值对象。</returns>
    public TValue? Get<TValue>(string key) => OnGet<TValue>(key);

    /// <summary>
    /// 增加缓存值。
    /// </summary>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public bool Add<TValue>(string key, TValue value)
    {
        Set(key, value);
        return true;
    }

    /// <summary>
    /// 增加缓存值。
    /// </summary>
    /// <typeparam name="TValue">值类型。</typeparam>
    /// <param name="key">键对象。</param>
    /// <param name="value">值对象。</param>
    public void Set<TValue>(string key, TValue value) => Set(key, value, TimeSpan.FromMinutes(20));

    /// <summary>
    /// 增加缓存值。
    /// </summary>
    /// <remarks>
    /// 传入对象时，必须是可序列化才能保存成功。
    /// 假如对象 A 中包含着对象 B。
    /// 则对象 B 也必须要标记为可序列化才可以保存成功。
    /// 注意：保存到缓存中的实体，必须是带有默认构造函数的实体。
    /// </remarks>
    /// <typeparam name="TValue">值类型。</typeparam>
    /// <param name="key">键对象。</param>
    /// <param name="value">值对象。</param>
    /// <param name="timeOut">有效时长。</param>
    public void Set<TValue>(string key, TValue value, TimeSpan timeOut) => OnSet(key, value, timeOut);

    /// <summary>
    /// 增加缓存值。
    /// </summary>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="key"></param>
    /// <param name="action"></param>
    /// <param name="timeOut"></param>
    /// <param name="lockTimeOut"></param>
    public void SetLock<TValue>(string key, Func<TValue, TValue> action, TimeSpan timeOut, TimeSpan lockTimeOut)
    {
        if (string.IsNullOrWhiteSpace(key))
            throw new ArgumentNullException(nameof(key), "key 不可以是空");
        if (action == null)
            throw new ArgumentNullException(nameof(action), "action 不可以是空");

        lock (_lockObject)
        {
            var value = Get<TValue>(key);
            if (value == null)
                throw new ArgumentNullException(nameof(value), "value 不可以是空");
            value = action(value);
            Set(key, value, timeOut);
        }
    }

    /// <summary>
    /// 为给定 key 设置过期时间。
    /// </summary>
    /// <param name="key"></param>
    /// <param name="timeOut">时间间隔。</param>
    /// <returns></returns>
    public bool Expire(string key, TimeSpan timeOut) => _client.Expire(GetKey(key), timeOut);

    /// <summary>
    /// 为给定 key 设置过期时间。
    /// </summary>
    /// <param name="key"></param>
    /// <param name="timeOut">过期时间。</param>
    /// <returns></returns>
    public bool ExpireAt(string key, DateTime timeOut) => _client.ExpireAt(GetKey(key), timeOut);

    /// <summary>
    /// 移除缓存值。
    /// </summary>
    /// <param name="key">键对象。</param>
    public bool Remove(string key) => _client.Del(GetKey(key)) > 0;

    /// <summary>
    /// 移除缓存值。
    /// </summary>
    /// <typeparam name="TValue">值类型。</typeparam>
    /// <param name="key">键对象。</param>
    /// <param name="result">值对象。</param>
    /// <returns></returns>
    public bool Remove<TValue>(string key, out TValue? result)
    {
        result = Get<TValue>(key);
        return Remove(key);
    }

    /// <summary>
    /// 清空缓存。
    /// </summary>
    public void Clear()
    {
        var keys = Keys;

        if (keys == null)
            return;

        if (!keys.Any())
            return;

        foreach (var key in keys)
            Remove(key);
    }

    #region --- 数值操作 ---

    /// <summary>
    /// 根据指定的 Key，将值加1(仅整型有效)。
    /// </summary>
    /// <param name="key"></param>
    public void IncrementValue(string key) => _client.IncrBy(GetKey(key), 1);

    /// <summary>
    /// 根据指定的Key，将值加上指定值(仅整型有效)。
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public void IncrementValueBy(string key, int value) => _client.IncrBy(GetKey(key), value);

    /// <summary>
    /// 根据指定的 Key，将值减1(仅整型有效)。
    /// </summary>
    /// <param name="key"></param>
    public void DecrementValue(string key) => IncrementValueBy(key, -1);

    /// <summary>
    /// 根据指定的 Key，将值减去指定值(仅整型有效)。
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public void DecrementValueBy(string key, int value) => IncrementValueBy(key, -value);

    #endregion

    #region --- 集合操作 ---

    /// <summary>
    /// 添加一个项到内部的 List。
    /// </summary>
    /// <param name="listId"></param>
    /// <param name="value"></param>
    public void AddItemToList(string listId, string value) => _client.SAdd(GetKey(listId), value);

    /// <summary>
    /// 一次过将参数中的 List 中的多个值添加入内部的 List。
    /// </summary>
    /// <param name="listId"></param>
    /// <param name="value"></param>
    public void AddRangeToList(string listId, IEnumerable<string> value)
    {
        if (value == null || !value.Any())
            return;
        _client.SAdd(GetKey(listId), value);
    }

    /// <summary>
    /// 移除指定 ListId 的内部 List 中第二个参数值相等的那一项。
    /// </summary>
    /// <param name="listId"></param>
    /// <param name="value"></param>
    public void RemoveItemFromList(string listId, string value) => _client.SRem(GetKey(listId), value);

    /// <summary>
    /// 获取指定 ListId 的内部 List 的所有值。
    /// </summary>
    /// <param name="listId"></param>
    /// <returns></returns>
    public IEnumerable<string> GetAllItemsFromList(string listId) => _client.Sort(GetKey(listId));

    #endregion

    #region --- 缓存操作 ---
    private string GetKey(string key) => $"freesql_{key}";
    private TValue? OnGet<TValue>(string key)
    {
        key = GetKey(key);
        var json = _client.Get(key);
        if (string.IsNullOrWhiteSpace(json))
            return default;
        var value = json.FromJson<CacheEntity<TValue>>();
        if (value == null)
            return default;
        return value.Value;
    }
    private void OnSet<TValue>(string key, TValue value, TimeSpan timeOut)
    {
        if (string.IsNullOrWhiteSpace(key))
            throw new ArgumentNullException(nameof(key), "key 不可以是空");
        if (value == null)
            throw new ArgumentNullException(nameof(value), "value 不可以是空");

        key = GetKey(key);
        var entity = new CacheEntity<TValue> { Value = value, Created = DateTime.Now, ExpiredTime = DateTime.Now.Add(timeOut) };
        _client.Set(key, entity.ToJson(), timeOut);
    }
    private class CacheEntity<TValue>
    {
        /// <summary>
        /// 缓存值。
        /// </summary>
        public TValue? Value { get; set; }

        /// <summary>
        /// 过期时间。
        /// </summary>
        public DateTime ExpiredTime { get; set; }

        /// <summary>
        /// 创建时间。
        /// </summary>
        public DateTime Created { get; set; }
    }
    #endregion
}